package 多线程;

//线程安全：
//
//如果多个线程在同时运行，这些线程可能会同时运行同一个数据，则可能会出现错误。
//
//电影院要卖票，我们模拟电影院的卖票过程。假设要播放的电影是 “功夫熊猫3”，本次电影的座位共100个(本场电影只能卖100张票)。
//
//我们来模拟电影院的售票窗口，实现多个窗口同时卖 “功夫熊猫3”这场电影票(多个窗口一起卖这100张票)。
//实例：

//public class ThreadDemo {
/*public class 线程安全 {
	public static void main(String[] args) {
		//创建票对象
		Ticket ticket = new Ticket();
		
		//创建3个窗口
		Thread t1  = new Thread(ticket, "窗口1");
		Thread t2  = new Thread(ticket, "窗口2");
		Thread t3  = new Thread(ticket, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

class Ticket implements Runnable {
	//共100票
	int ticket = 100;

	@Override
	public void run() {
		//模拟卖票
		while(true){
			if (ticket > 0) {
				//模拟选坐的操作
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
			}
		}
	}
}*/
//窗口1正在卖票：3
//窗口2正在卖票：2
//窗口3正在卖票：1
//窗口2正在卖票：-1
//窗口1正在卖票：0
//运行结果发现：上面程序出现了问题
//
//票出现了重复的票
//
//错误的票 0、-1
//
//解析：在上述代码while语句的if语句内，存在线程安全隐患，比如当前票数还剩下1张，窗口1、2、3正在抢票，若窗口1抢到了CPU的处理片，
//进入if语句内，进入try语句内，则程序进入休眠状态，窗口2或3其中之一抢到了CPU的处理片，再次进入if语句内，满足ticket > 0的条件，
//也再次进入了休眠状态，当窗口1过了sleep时间后，执行剩余操作，将ticket--，则ticket = 0，然后窗口2过了sleep时间后，再次执行剩
//余操作，将ticket--，则ticket = -1，所以出现了上述结果。Java提供了线程同步机制，能够解决线程安全问题。

//线程安全的解决办法：
//
//1. 线程同步
//
//2. Lock接口
//
//1.线程同步：

//线程同步的方式有两种：
//
//方式1：同步代码块
//
//方式2：同步方法
//
//方式一同步代码块：
//
//在代码块声明时加上 synchronized关键字。
//synchronized（锁对象）{
//    可能会产生线程安全问题的代码
//}
//同步代码块中的锁对象可以是任意的对象；但多个线程时，要使用同有个锁对象才能保证线程安全。
//
//实例：

//public class Ticket implements Runnable {
//	//共100票
//	int ticket = 100;
//	//定义锁对象
//	Object lock = new Object();
//	@Override
//	public void run() {
//		//模拟卖票
//		while(true){
//			//同步代码块
//			synchronized (lock){
//				if (ticket > 0) {
//					//模拟电影选坐的操作
//					try {
//						Thread.sleep(10);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
//				}
//			}
//		}
//	}
//}
//在有线程任务的代码块中用synchronized(锁对象)来包裹起来。这样就可以解决线程安全问题，但前提是当多个线程同时使用一个线程任务时，必须保证锁对象
//唯一，不然仍然会出现安全问题。
//方式二同步方法：
//
//同步方法：在方法声明上加上synchronized关键字，可以在线程代码run方法中写一个用synchronized关键字修饰的函数，如：
//public synchronized void method(){
//   	可能会产生线程安全问题的代码
//}
//同步方法中的锁对象是 this
//public class Ticket implements Runnable {
//	//共100票
//	int ticket = 100;
//	//定义锁对象
//	Object lock = new Object();
//	@Override
//	public void run() {
//		//模拟卖票
//		while(true){
//			//同步方法
//			method();
//		}
//	}
// 
////同步方法,锁对象this
//	public synchronized void method(){
//		if (ticket > 0) {
//			//模拟选坐的操作
//			try {
//				Thread.sleep(10);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
//		}
//	}
//}
//静态同步方法: 在方法声明上加上staticsynchronized
//
//public static synchronized void method(){
//可能会产生线程安全问题的代码
//}
//静态同步方法中的锁对象是  类名.class
//
//2.Lock接口：
//
//Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
//void lock() 获取锁。
//void unlock() 释放锁。
//实例：更简单。
//
//public class Ticket implements Runnable {
//	//共100票
//	int ticket = 100;
//	
//	//创建Lock锁对象
//	Lock ck = new ReentrantLock();
//	
//	@Override
//	public void run() {
//		//模拟卖票
//		while(true){
//			//synchronized (lock){
//			ck.lock();
//				if (ticket > 0) {
//					//模拟选坐的操作
//					try {
//						Thread.sleep(10);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
//				}
//			ck.unlock();
//			//}
//		}
//	}
//}
//
//死锁：
//
//同步锁使用的弊端：当线程任务中出现了多个同步(多个锁)时，如果同步中嵌套了其他的同步。这时容易引发一种现象：程序出现无限等待，这种现象我们
//称为死锁。这种情况能避免就避免掉。
//
//synchronzied(A锁){
//	synchronized(B锁){
//         
//  }
//}
//
//
//等待唤醒机制：
//
//在开始讲解等待唤醒机制之前，有必要搞清一个概念——线程之间的通信：多个线程在处理同一个资源，但是处理的动作（线程的任务）却不相同。通过一定的手段
//使各个线程能有效的利用资源。而这种手段即——等待唤醒机制。
//
//比如：实现一功能，input为一线程，output为一线程，main为主线程，实现input对成员变量赋值，output获取成员变量值。
//但有一个问题是当input给一个成员变量赋值后，output开始获取成员变量值，此时还没有获取完全，紧接着input给下一个成员变量赋值，这就导致了output
//获取的值可能是input第二次给成员变量赋的值，要想解决此办法，必须让input和output这两个线程实现通信，保证一边input赋值后，等待output获取值，
//当其获取值后，给input一个信号，使其再赋值。才能保证不会出现乱值。
//
//等待唤醒机制所涉及到的方法：
//
// wait（） :等待，将正在执行的线程释放其执行资格 和 执行权，并存储到线程池中。
//
// notify（）：唤醒，唤醒线程池中被wait（）的线程，一次唤醒一个，而且是任意的。
//
// notifyAll（）： 唤醒全部：可以将线程池中的所有wait() 线程都唤醒。
//
//1.当input发现Resource中没有数据时，开始输入，输入完成后，叫output来输出。如果发现有数据，就wait();
//
//2.当output发现Resource中没有数据时，就wait() ；当发现有数据时，就输出，然后，叫醒input来输入数据。
/*class Resource {
	private String name;
	private String sex;
	private boolean flag = false;
 
	public synchronized void set(String name, String sex) {
		if (flag)
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		// 设置成员变量
		this.name = name;
		this.sex = sex;
		// 设置之后，Resource中有值，将标记该为 true ,
		flag = true;
		// 唤醒output
		this.notify();
	}
 
	public synchronized void out() {
		if (!flag)
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		// 输出线程将数据输出
		System.out.println("姓名: " + name + "，性别: " + sex);
		// 改变标记，以便输入线程输入数据
		flag = false;
		// 唤醒input，进行数据输入
		this.notify();
	}
}

class Input implements Runnable {
	private Resource r;
 
	public Input(Resource r) {
		this.r = r;
	}
 
	@Override
	public void run() {
		int count = 0;
		while (true) {
			if (count == 0) {
				r.set("小明", "男生");
			} else {
				r.set("小花", "女生");
			}
			// 在两个数据之间进行切换
			count = (count + 1) % 2;
		}
	}
}

class Output implements Runnable {
	private Resource r;
 
	public Output(Resource r) {
		this.r = r;
	}
 
	@Override
	public void run() {
		while (true) {
			r.out();
		}
	}
}

//public class ResourceDemo {
public class 线程安全 {
	public static void main(String[] args) {
		// 资源对象
		Resource r = new Resource();
		// 任务对象
		Input in = new Input(r);
		Output out = new Output(r);
		// 线程对象
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		// 开启线程
		t1.start();
		t2.start();
	}
}*/

//线程池
//
//线程池概念：
//
//线程池，其实就是一个容纳多个线程的容器，其中的线程可以反复的使用，省去了频繁创建线程对象的操作，无需反复创建线程而消耗过多资源。
//
//我们详细的解释一下为什么要使用线程池？
//
//在java中，如果每个请求到达就创建一个新线程，开销是相当大的。在实际使用中，创建和销毁线程花费的时间和消耗的系统资源都相当大，甚至可能要比在处理实
//际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外，活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程，可能会使系统由于过
//度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足，需要采取一些办法来限制任何给定时刻处理的请求数目，尽可能减少创建和销毁线程的次数，
//特别是一些资源耗费比较大的线程的创建和销毁，尽量利用已有对象来进行服务。
//
//线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程，线程创建的开销就被分摊到了多个任务上了，而且由于在请求到达时线程
//已经存在，所以消除了线程创建所带来的延迟。这样，就可以立即为请求服务，使用应用程序响应更快。另外，通过适当的调整线程中的线程数目可以防止出现资源不
//足的情况。

//使用线程池方式：
//
//1. Runnable接口
//
//2. Callable接口
//
//方式一：
//
//实例：
//
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/*
 *  JDK1.5新特性,实现线程池程序步骤：
 *  1.使用工厂类 Executors中的静态方法创建线程对象,指定线程的个数
 *   static ExecutorService newFixedThreadPool(int 个数) 返回线程池对象
 *   返回的是ExecutorService接口的实现类 (线程池对象)
 *  2.创建Runnable类的对象，作为submit方法中的参数
 *   接口实现类对象,调用方法submit (Ruunable r) 提交线程执行任务
 *          
 */
//public class ThreadPoolDemo {
public class 线程安全 {
	public static void main(String[] args) {
		//调用工厂类的静态方法,创建线程池对象
		//返回线程池对象,是返回的接口
		ExecutorService es = Executors.newFixedThreadPool(2);
	    //调用接口实现类对象es中的方法submit提交线程任务
		//将Runnable接口实现类对象,传递
		es.submit(new ThreadPoolRunnable());//采用匿名对象
		es.submit(new ThreadPoolRunnable());
		es.submit(new ThreadPoolRunnable());
	
	}
}

class ThreadPoolRunnable implements Runnable {
	public void run(){
		System.out.println(Thread.currentThread().getName()+" 线程提交任务");
	}
}
//
//方式二：
//实例：
//
//import java.util.concurrent.ExecutorService;
//import java.util.concurrent.Executors;
//import java.util.concurrent.Future;
// 
///*
// *  实现线程程序的第三个方式,实现Callable接口方式
// *  实现步骤
// *    1.工厂类 Executors静态方法newFixedThreadPool方法,创建线程池对象
// *    线程池对象ExecutorService接口实现类,调用方法submit提交线程任务
// *    2.创建Callable接口实现类的对象作为submit的参数 submit(Callable c)
// */
//public class ThreadPoolDemo1 {
//	public static void main(String[] args)throws Exception {
//		ExecutorService es = Executors.newFixedThreadPool(2);
//		//提交线程任务的方法submit方法返回 Future接口的实现类
//		Future<String> f = es.submit(new ThreadPoolCallable());
//		String s = f.get();
//		System.out.println(s);
//	}
//}
//
//import java.util.concurrent.Callable;
// 
//public class ThreadPoolCallable implements Callable<String>{
//	public String call(){
//		return "abc";
//	}
//}
//
//采用此方法的优点在于：
//Callable接口返回结果并且可能抛出异常的任务，该接口类中的方法call()，可以返回任意类型数据，会抛出异常，但是Runnable接口类中存放任务
//的方法run(),返回类型为void类型。建议使用第二种方式。



